package com.hmy.finance.service.receipt.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hmy.ccp.error.exception.HMYRuntimeException;
import com.hmy.finance.api.common.enums.receipt.OffsetStatusEnum;
import com.hmy.finance.common.constants.CommonConstants;
import com.hmy.finance.common.enums.PaymentStatusEnum;
import com.hmy.finance.common.properties.ReceivableInventoryOverdueRule;
import com.hmy.finance.dal.mapper.receipt.ContractOffsetRecordMapper;
import com.hmy.finance.dal.po.DeviceDailyBillPo;
import com.hmy.finance.dal.po.DeviceRentalRecordPo;
import com.hmy.finance.dal.po.ProfitLossRecordPo;
import com.hmy.finance.dal.po.ReceivableInventoryPo;
import com.hmy.finance.dal.po.json.OverdueDetailJson;
import com.hmy.finance.dal.po.receipt.ContractOffsetRecordPo;
import com.hmy.finance.dal.po.receipt.json.OffsetBillDetailInfo;
import com.hmy.finance.service.core.IDeviceDailyBillService;
import com.hmy.finance.service.core.IDeviceRentalRecordService;
import com.hmy.finance.service.core.IProfitLossRecordService;
import com.hmy.finance.service.core.IReceivableInventoryService;
import com.hmy.finance.service.receipt.CustomerContactorBalanceAccountService;
import com.hmy.finance.service.receipt.IncomeExpensesService;
import com.hmy.finance.service.receipt.OffsetFeeBillService;
import com.hmy.finance.service.receipt.dto.contactorAccount.ContactorBalanceQueryDto;
import com.hmy.finance.service.receipt.dto.offset.*;
import com.hmy.finance.service.receipt.rdto.TradeRdto;
import com.hmy.finance.service.receipt.rdto.contactorAccount.ContactorBalanceQueryRdto;
import com.hmy.finance.service.receipt.rdto.offset.AutomaticOffsetRdto;
import com.hmy.finance.service.receipt.rdto.offset.ManualOffsetRdto;
import com.hmy.finance.service.receipt.rdto.offset.OffsetContractDailyFeeBIllRdto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * implement of OffsetFeeBillService definition methods
 *
 * @author songkf
 */
@Slf4j
@Service
public class OffsetFeeBillServiceImpl implements OffsetFeeBillService {
    @Resource
    private IncomeExpensesService incomeExpensesService;
    @Resource
    private ContractOffsetRecordMapper contractOffsetRecordMapper;
    @Resource
    private DefaultIdentifierGenerator defaultIdentifierGenerator;
    @Resource
    private IReceivableInventoryService receivableInventoryService;
    @Resource
    private IDeviceDailyBillService deviceDailyBillService;
    @Resource
    private IProfitLossRecordService profitLossRecordService;
    @Resource
    private CustomerContactorBalanceAccountService customerContactorBalanceAccountService;
    @Resource
    private OffsetServiceImpl offsetServiceImpl;
    @Resource
    private IDeviceRentalRecordService deviceRentalRecordService;
    @Resource
    private ReceivableInventoryOverdueRule overdueRule;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public OffsetContractDailyFeeBIllRdto offsetContractDailyFeeBill(OffsetContractDailyFeeBillDto offsetContractDailyFeeBillDto) {
        log.info("[Offset-Contract Daily Bill] - HandleDate : {} - ContractId : {} - FeeBillIncurredDate : {}", LocalDate.now(), offsetContractDailyFeeBillDto.getContractId(), offsetContractDailyFeeBillDto.getFeeBillIncurredDate());
        // 1.loop offsetContractDailyFeeBillDto receivableAdjustProfitLossInfoList and waitOffsetDeviceDailyBillInfoList , build OffsetBillDetailInfo list
        List<OffsetBillDetailInfo> offsetBillDetailInfoList = new ArrayList<>();
        for (ProfitLossRecordPo receivableAdjustProfitLossInfo : offsetContractDailyFeeBillDto.getReceivableAdjustProfitLossInfoList()) {
            OffsetBillDetailInfo offsetBillDetailInfo = buildOffsetBillDetailInfo(receivableAdjustProfitLossInfo);
            offsetContractDailyFeeBillDto.setOffsetTotalAmount(offsetContractDailyFeeBillDto.getOffsetTotalAmount().add(offsetBillDetailInfo.getBillTotalAmount()));
            offsetBillDetailInfoList.add(offsetBillDetailInfo);
        }
        for (DeviceDailyBillPo waitOffsetDeviceDailyBillInfo : offsetContractDailyFeeBillDto.getWaitOffsetDeviceDailyBillInfoList()) {
            OffsetBillDetailInfo offsetBillDetailInfo = buildOffsetBillDetailInfo(waitOffsetDeviceDailyBillInfo);
            offsetContractDailyFeeBillDto.setOffsetTotalAmount(offsetContractDailyFeeBillDto.getOffsetTotalAmount().add(offsetBillDetailInfo.getBillTotalAmount()));
            offsetBillDetailInfoList.add(offsetBillDetailInfo);
        }
        for (OffsetDeviceDailyBillDto offsetDeviceDailyBillDto : offsetContractDailyFeeBillDto.getWaitOffsetDeviceDailyBillDtoList()) {
            OffsetBillDetailInfo offsetBillDetailInfo = buildOffsetBillDetailInfo(offsetDeviceDailyBillDto);
            offsetContractDailyFeeBillDto.setOffsetTotalAmount(offsetContractDailyFeeBillDto.getOffsetTotalAmount().add(offsetBillDetailInfo.getBillTotalAmount()));
            offsetBillDetailInfoList.add(offsetBillDetailInfo);
        }
        // 2.build ContractOffsetRecordPo Object
        ContractOffsetRecordPo contractOffsetRecordPo = buildContractOffsetRecordPo(offsetContractDailyFeeBillDto.getContractId(), offsetContractDailyFeeBillDto.getFeeBillIncurredDate().atTime(LocalTime.MAX), offsetBillDetailInfoList, offsetContractDailyFeeBillDto.getSubmitUserId(), offsetContractDailyFeeBillDto.getSubmitUserName());
        contractOffsetRecordPo.setAmount(offsetContractDailyFeeBillDto.getOffsetTotalAmount());
        contractOffsetRecordPo.setContactorId(offsetContractDailyFeeBillDto.getContactorId());
        int insert = contractOffsetRecordMapper.insert(contractOffsetRecordPo);
        if (insert <= 0) {
            throw new HMYRuntimeException(506700001);
        }
        if (contractOffsetRecordPo.getAmount().compareTo(BigDecimal.ZERO) == 0) {
            return OffsetContractDailyFeeBIllRdto.builder().contractOffsetRecordId(contractOffsetRecordPo.getId()).actualOffsetAmount(contractOffsetRecordPo.getAmount()).offsetStatus(contractOffsetRecordPo.getOffsetStatus());
        }
        // 3.build ContactorExpensesDto Object
        TradeRdto tradeRdto = offsetServiceImpl.offset(contractOffsetRecordPo);
        if (tradeRdto.isSuccess()) {
            return OffsetContractDailyFeeBIllRdto.builder().contractOffsetRecordId(contractOffsetRecordPo.getId()).actualOffsetAmount(tradeRdto.getAmount()).offsetStatus(contractOffsetRecordPo.getOffsetStatus());
        } else {
            throw new HMYRuntimeException(506700001);
        }
    }

    private OffsetBillDetailInfo buildOffsetBillDetailInfo(OffsetDeviceDailyBillDto offsetDeviceDailyBillDto) {
        OffsetBillDetailInfo offsetBillDetailInfo = new OffsetBillDetailInfo();
        offsetBillDetailInfo.setOffsetBillId(offsetDeviceDailyBillDto.getDeviceDailyBillId());
        offsetBillDetailInfo.setOffsetBillType(1);
        offsetBillDetailInfo.setBillTotalAmount(offsetDeviceDailyBillDto.getUnpaidAmount());
        offsetBillDetailInfo.setFeeIncurredTime(offsetDeviceDailyBillDto.getFeeIncurredTime());
        return offsetBillDetailInfo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ManualOffsetRdto manualOffset(ManualOffsetDto manualOffsetDto) {
        //1.loop manualOffsetDto#contractOffsetDetailList and build ContractOffsetRecordPo Object
        List<ContractOffsetRecordPo> contractOffsetRecordList = new ArrayList<>(manualOffsetDto.getContractOffsetDetailList().size());
        List<ManualOffsetRdto.ContractOffsetResult> contractOffsetResultList = new ArrayList<>(manualOffsetDto.getContractOffsetDetailList().size());
        for (ManualOffsetDto.ContractOffsetDetail contractOffsetDetail : manualOffsetDto.getContractOffsetDetailList()) {
            ContractOffsetRecordPo contractOffsetRecordPo = buildContractOffsetRecordPo(contractOffsetDetail);
            contractOffsetRecordPo.setCreatorId(manualOffsetDto.getSubmitUserId());
            contractOffsetRecordPo.setCreatorName(manualOffsetDto.getSubmitUserName());
            contractOffsetRecordPo.setUpdaterId(manualOffsetDto.getSubmitUserId());
            contractOffsetRecordPo.setUpdaterName(manualOffsetDto.getSubmitUserName());
            contractOffsetRecordPo.setOffsetStatus(OffsetStatusEnum.WAIT);
            contractOffsetRecordPo.setReceiptOffsetBillId(manualOffsetDto.getReceiptOffsetBillId());
            contractOffsetRecordPo.setId(defaultIdentifierGenerator.nextId(contractOffsetRecordPo));
            contractOffsetRecordPo.setIsDeleted(CommonConstants.NOT_DELETED);
            contractOffsetRecordList.add(contractOffsetRecordPo);
            //Build ManualOffsetRdto ContractOffsetResult
            ManualOffsetRdto.ContractOffsetResult contractOffsetResult = new ManualOffsetRdto.ContractOffsetResult();
            contractOffsetResult.setContactorId(contractOffsetDetail.getContactorId());
            contractOffsetResult.setContractId(contractOffsetRecordPo.getContractId());
            contractOffsetResult.setActualOffsetAmount(contractOffsetRecordPo.getActualOffsetAmount());
            contractOffsetResult.setOffsetStatus(contractOffsetRecordPo.getOffsetStatus());
            contractOffsetResultList.add(contractOffsetResult);
        }

        // 2. insert ContractOffsetRecordPo
        int insert = contractOffsetRecordMapper.batchSave(contractOffsetRecordList);
        if (insert <= 0) {
            throw new HMYRuntimeException(506700001);
        }
        ManualOffsetRdto manualOffsetRdto = new ManualOffsetRdto();
        manualOffsetRdto.setContractOffsetResultList(contractOffsetResultList);
        manualOffsetRdto.setResult(true);
        //start manual offset
        offsetServiceImpl.offsetManualContractBill(manualOffsetDto.getReceiptOffsetBillId());
        return manualOffsetRdto;
    }

    @Override
    public AutomaticOffsetRdto automaticOffset(AutomaticOffsetDto automaticOffsetDto) {
        //Has money customer id list and contactor id list
        List<Long> hasMoneyCustomerOrContactorIdList = new ArrayList<>();
        List<Long> hasNotMoneyCustomerOrContactorIdList = new ArrayList<>();
        //1. Select Need Offset Receivable Inventory List(means the receivableInventoryPo's totalReceivableAmount greater than 0)
        List<ReceivableInventoryPo> receivableInventoryPos = receivableInventoryService.selectWaitOffsetList(automaticOffsetDto.getCustomerId(), automaticOffsetDto.getContractId(), null, null);
        //2. loop receivableInventoryPos check customer and contactor balance account has money
        List<ContractOffsetPeriodDto> contractOffsetPeriodDtoList = new ArrayList<>();
        receivableInventoryPos.forEach(receivableInventory -> {
            //2.1 check customer and contactor balance account has money
            Boolean hasMoney = Boolean.FALSE;
            if (hasMoneyCustomerOrContactorIdList.contains(receivableInventory.getCustomerId()) || hasMoneyCustomerOrContactorIdList.contains(receivableInventory.getSignatoryId())) {
                hasMoney = Boolean.TRUE;
            } else if (hasNotMoneyCustomerOrContactorIdList.contains(receivableInventory.getCustomerId()) && hasNotMoneyCustomerOrContactorIdList.contains(receivableInventory.getSignatoryId())) {
                hasMoney = Boolean.FALSE;
            } else {
                ContactorBalanceQueryDto contactorBalanceQueryDto = new ContactorBalanceQueryDto();
                contactorBalanceQueryDto.setCustomerId(receivableInventory.getCustomerId());
                contactorBalanceQueryDto.setContactorId(receivableInventory.getSignatoryId());
                ContactorBalanceQueryRdto contactorBalanceQueryRdto = customerContactorBalanceAccountService.queryContactorBalance(contactorBalanceQueryDto);
                if (Objects.nonNull(contactorBalanceQueryRdto)) {
                    //2.2 if customer and contactor balance account has money，set hasMoney to true
                    if (contactorBalanceQueryRdto.getCustomerBalanceAmount().compareTo(BigDecimal.ZERO) > 0) {
                        hasMoney = Boolean.TRUE;
                        hasMoneyCustomerOrContactorIdList.add(receivableInventory.getCustomerId());
                    } else {
                        hasNotMoneyCustomerOrContactorIdList.add(receivableInventory.getCustomerId());
                    }
                    if (contactorBalanceQueryRdto.getContactorBalanceAmount().compareTo(BigDecimal.ZERO) > 0) {
                        hasMoney = Boolean.TRUE;
                        hasMoneyCustomerOrContactorIdList.add(receivableInventory.getSignatoryId());
                    } else {
                        hasNotMoneyCustomerOrContactorIdList.add(receivableInventory.getSignatoryId());
                    }
                }
            }
            //2.3 if hasMoney is true, build ContractOffsetPeriodDto
            if (hasMoney) {
                contractOffsetPeriodDtoList.addAll(groupingContractAccountPeriod(receivableInventory));
            }
        });
        //3. Sorted contractOffsetPeriodDtoList by periodSort And Contract sign time
        //有限核销账龄大的，如果账龄相同有限核销合同签署时间早的
        contractOffsetPeriodDtoList.sort(Comparator.comparing(ContractOffsetPeriodDto::getPeriodSort).thenComparing(ContractOffsetPeriodDto::getContractSignTime));
        //4. loop contractOffsetPeriodDtoList and build OffsetContractDailyFeeBillDto
        contractOffsetPeriodDtoList.forEach(contractOffsetPeriodDto -> {
            OffsetContractDailyFeeBillDto offsetContractDailyFeeBillDto = new OffsetContractDailyFeeBillDto();
            offsetContractDailyFeeBillDto.setContractId(contractOffsetPeriodDto.getContractId());
            offsetContractDailyFeeBillDto.setContactorId(contractOffsetPeriodDto.getContactorId());
            offsetContractDailyFeeBillDto.setFeeBillIncurredDate(LocalDate.now());
            offsetContractDailyFeeBillDto.setSubmitUserId(-1L);
            offsetContractDailyFeeBillDto.setSubmitUserName("SYSTEM-AUTO");
            offsetContractDailyFeeBillDto.setOffsetTotalAmount(BigDecimal.ZERO);
            //4.1 Select the Device Rental Record List by the contract id
            List<DeviceRentalRecordPo> deviceRentalRecordPoList = deviceRentalRecordService.list(Wrappers.lambdaQuery(DeviceRentalRecordPo.class).eq(DeviceRentalRecordPo::getContractId, contractOffsetPeriodDto.getContractId()));
            //4.2 loop deviceRentalRecordPoList ，if DeviceRentalRecordPo#overdueInfoList is not empty,and this overdue period is equals contractOffsetPeriodDto#periodKey And overdueAmount greater than 0
            List<OffsetDeviceDailyBillDto> waitOffsetDeviceDailyBillPoList = new ArrayList<>();
            for (DeviceRentalRecordPo deviceRentalRecordPo : deviceRentalRecordPoList) {
                if (CollectionUtils.isEmpty(deviceRentalRecordPo.getOverdueInfoList())) {
                    continue;
                }
                Optional<OverdueDetailJson> overdueDetailJsonOption = deviceRentalRecordPo.getOverdueInfoList().stream().filter(overdueInfo -> Objects.nonNull(overdueInfo.getOverdueDaysKey()) && overdueInfo.getOverdueDaysKey().equals(contractOffsetPeriodDto.getPeriodKey()) && overdueInfo.getOverdueAmount().compareTo(BigDecimal.ZERO) > 0).findFirst();
                if (overdueDetailJsonOption.isPresent()) {
                    OverdueDetailJson overdueDetailJson = overdueDetailJsonOption.get();
                    List<DeviceDailyBillPo> deviceDailyBillList = deviceDailyBillService.queryDeviceDailyBillByDeviceRentalRecordIdWithDateArea(deviceRentalRecordPo.getId(), overdueDetailJson.getOverdueStartDate(), overdueDetailJson.getOverdueEndDate());
                    OffsetDeviceDailyBillDto offsetDeviceDailyBillDto = null;
                    for (DeviceDailyBillPo deviceDailyBill : deviceDailyBillList) {
                        offsetDeviceDailyBillDto = OffsetDeviceDailyBillDto.buildFromDeviceDailyBill(deviceDailyBill);
                        offsetDeviceDailyBillDto.setIsTermination(Objects.nonNull(deviceRentalRecordPo.getRentalEndTime()));
                        waitOffsetDeviceDailyBillPoList.add(offsetDeviceDailyBillDto);
                    }
                }
            }
            offsetContractDailyFeeBillDto.setWaitOffsetDeviceDailyBillDtoList(waitOffsetDeviceDailyBillPoList);
            //4.3 Select the Contract Profit Loss Record List by the contract id
            //TODO 2024年6月12日13:40:59 此处关于合同的损益数据（应收调整、物流放空费），由于产品尚未确定好核销逻辑暂未计算在核销范围之内
            OffsetContractDailyFeeBIllRdto offsetContractDailyFeeBIllRdto = this.offsetContractDailyFeeBill(offsetContractDailyFeeBillDto);
            log.info("Offset Contract Daily Fee Bill Result: [ContractId - {}] [contractOffsetRecordId - {}] [offsetAmount - {}] [offsetStatus - {}]", offsetContractDailyFeeBillDto.getContractId(), offsetContractDailyFeeBIllRdto.getContractOffsetRecordId(), offsetContractDailyFeeBIllRdto.getActualOffsetAmount(), offsetContractDailyFeeBIllRdto.getOffsetStatus());
        });
        return AutomaticOffsetRdto.success();
    }

    private List<ContractOffsetPeriodDto> groupingContractAccountPeriod(ReceivableInventoryPo receivableInventory) {
        List<ContractOffsetPeriodDto> contractOffsetPeriodDtoList = new ArrayList<>();
        //1. Overdue Period
        receivableInventory.getOverdueDetail().forEach(overdueDetail -> {
            if (overdueDetail.getOverdueAmount().compareTo(BigDecimal.ZERO) == 0) {
                return;
            }
            ContractOffsetPeriodDto overduePeriod = buildAccountPeriod(receivableInventory.getCustomerId(), receivableInventory.getSignatoryId(), receivableInventory.getContractId(), receivableInventory.getContractSignTime(), null, overdueRule.getRuleByDays(overdueDetail.getOverdueDaysStart(), overdueDetail.getOverdueDaysEnd()));
            if (Objects.nonNull(overduePeriod)) {
                contractOffsetPeriodDtoList.add(overduePeriod);
            }
        });
        //2.Current Period. if current_amount greater than 0, build Current Period
        if (receivableInventory.getCurrentAmount().compareTo(BigDecimal.ZERO) > 0) {
            //2.1 Current Period (CURRENT)
            ContractOffsetPeriodDto current = buildAccountPeriod(receivableInventory.getCustomerId(), receivableInventory.getSignatoryId(), receivableInventory.getContractId(), receivableInventory.getContractSignTime(), "CURRENT", null);
            if (Objects.nonNull(current)) {
                contractOffsetPeriodDtoList.add(current);
            }
        }

        //3. UNEXPIRED Period. if unexpired_amount greater than 0, build UNEXPIRED Period
        if (receivableInventory.getUnexpiredAmount().compareTo(BigDecimal.ZERO) > 0) {
            //3.1 UNEXPIRED Period (UNEXPIRED)
            ContractOffsetPeriodDto unexpired = buildAccountPeriod(receivableInventory.getCustomerId(), receivableInventory.getSignatoryId(), receivableInventory.getContractId(), receivableInventory.getContractSignTime(), "UNEXPIRED", null);
            if (Objects.nonNull(unexpired)) {
                contractOffsetPeriodDtoList.add(unexpired);
            }
        }
        return contractOffsetPeriodDtoList;
    }

    private ContractOffsetPeriodDto buildAccountPeriod(Long customerId, Long contactorId, Long contractId, LocalDateTime contractSignTime, String periodKey, ReceivableInventoryOverdueRule.Rule rule) {
        if (Objects.isNull(rule) && StringUtils.isNotBlank(periodKey)) {
            rule = overdueRule.getRule(periodKey);
        }
        if (Objects.isNull(rule)) {
            return null;
        }
        ContractOffsetPeriodDto contractOffsetPeriodDto = new ContractOffsetPeriodDto();
        contractOffsetPeriodDto.setCustomerId(customerId);
        contractOffsetPeriodDto.setContactorId(contactorId);
        contractOffsetPeriodDto.setContractId(contractId);
        contractOffsetPeriodDto.setContractSignTime(contractSignTime);
        contractOffsetPeriodDto.setPeriodKey(rule.getRuleKey());
        contractOffsetPeriodDto.setPeriodName(rule.getRuleName());
        contractOffsetPeriodDto.setPeriodSort(rule.getRuleSort());
        return contractOffsetPeriodDto;
    }

    private ContractOffsetRecordPo buildContractOffsetRecordPo(ManualOffsetDto.ContractOffsetDetail contractOffsetDetail) {
        //Select the Device daily bill object corresponding to the offset through date and belongs to the contract
        List<DeviceDailyBillPo> deviceDailyBillPoList = deviceDailyBillService.selectByContractIdAndOffsetThroughDate(contractOffsetDetail.getContractId(), null, null);
        //Select the Receivable Adjust ProfitLoss Info List by the offset through date and belongs to the contract
        List<ProfitLossRecordPo> profitLossRecordPoList = profitLossRecordService.selectReceivableAdjustListByContractIdAndOffsetThroughDate(contractOffsetDetail.getContractId(), null, null);
        List<OffsetBillDetailInfo> offsetBillDetailInfoList = new ArrayList<>();
        for (ProfitLossRecordPo receivableAdjustProfitLossInfo : profitLossRecordPoList) {
            offsetBillDetailInfoList.add(buildOffsetBillDetailInfo(receivableAdjustProfitLossInfo));
        }
        for (DeviceDailyBillPo waitOffsetDeviceDailyBillInfo : deviceDailyBillPoList) {
            offsetBillDetailInfoList.add(buildOffsetBillDetailInfo(waitOffsetDeviceDailyBillInfo));
        }
        log.info("offsetBillDetailInfoList: {}", JSONUtil.toJsonStr(offsetBillDetailInfoList));
        //offsetBillDetailInfoList Sorted by OffsetBillDetailInfo#feeIncurredTime asc
        offsetBillDetailInfoList.sort(Comparator.comparing(OffsetBillDetailInfo::getFeeIncurredTime));
        //loop offsetBillDetailInfoList apportion ContractOffsetDetail#offsetMaximumAmount
        BigDecimal offsetMaximumAmount = contractOffsetDetail.getOffsetMaximumAmount();
        BigDecimal actualOffsetAmount = BigDecimal.ZERO;
        List<OffsetBillDetailInfo> actualOffsetBillDetailList = new ArrayList<>();
        for (OffsetBillDetailInfo offsetBillDetailInfo : offsetBillDetailInfoList) {
            //Compare offsetMaximumAmount with offsetBillDetailInfo#billTotalAmount and set offsetBillDetailInfo#actualOffsetAmount
            if (offsetMaximumAmount.compareTo(offsetBillDetailInfo.getBillTotalAmount()) >= 0) {
                offsetMaximumAmount = offsetMaximumAmount.subtract(offsetBillDetailInfo.getBillTotalAmount());
                offsetBillDetailInfo.setActualOffsetAmount(offsetBillDetailInfo.getBillTotalAmount());
                offsetBillDetailInfo.setOffsetStatus(3);
                actualOffsetBillDetailList.add(offsetBillDetailInfo);
                actualOffsetAmount = actualOffsetAmount.add(offsetBillDetailInfo.getActualOffsetAmount());
            } else if (offsetMaximumAmount.compareTo(BigDecimal.ZERO) > 0) {
                offsetBillDetailInfo.setActualOffsetAmount(offsetMaximumAmount);
                offsetBillDetailInfo.setOffsetStatus(2);
                offsetMaximumAmount = BigDecimal.ZERO;
                actualOffsetBillDetailList.add(offsetBillDetailInfo);
                actualOffsetAmount = actualOffsetAmount.add(offsetBillDetailInfo.getActualOffsetAmount());
            }
        }
        ContractOffsetRecordPo contractOffsetRecordPo = buildContractOffsetRecordPo(contractOffsetDetail.getContractId(), LocalDate.now().atTime(LocalTime.MAX), actualOffsetBillDetailList, null, null);
        contractOffsetRecordPo.setAmount(actualOffsetAmount);
        contractOffsetRecordPo.setOffsetStatus(OffsetStatusEnum.WAIT);
        contractOffsetRecordPo.setContactorId(contractOffsetDetail.getContactorId());
        return contractOffsetRecordPo;
    }


    private static void handleBusinessBillOffset(OffsetContractDailyFeeBillDto offsetContractDailyFeeBillDto, ContractOffsetRecordPo contractOffsetRecordPo, BigDecimal actualOffsetTotalAmount) {
        for (OffsetBillDetailInfo offsetBillDetailInfo : contractOffsetRecordPo.getOffsetBillDetailInfos()) {
            // if actualOffsetTotalAmount greater than or equals offsetBillDetailInfo.getBillTotalAmount(), set offsetBillDetailInfo's Actual Offset Amount to offsetBillDetailInfo.getBillTotalAmount()
            if (actualOffsetTotalAmount.compareTo(offsetBillDetailInfo.getBillTotalAmount()) >= 0) {
                offsetBillDetailInfo.setOffsetStatus(3);
                offsetBillDetailInfo.setActualOffsetAmount(offsetBillDetailInfo.getBillTotalAmount());
                actualOffsetTotalAmount = actualOffsetTotalAmount.subtract(offsetBillDetailInfo.getBillTotalAmount());
            } else if (actualOffsetTotalAmount.compareTo(BigDecimal.ZERO) > 0) {
                offsetBillDetailInfo.setOffsetStatus(2);
                // else set offsetBillDetailInfo's Actual Offset Amount to actualOffsetTotalAmount
                offsetBillDetailInfo.setActualOffsetAmount(actualOffsetTotalAmount);
                actualOffsetTotalAmount = actualOffsetTotalAmount.subtract(actualOffsetTotalAmount);
            } else {
                offsetBillDetailInfo.setOffsetStatus(1);
                offsetBillDetailInfo.setActualOffsetAmount(BigDecimal.ZERO);
            }
            // 3.5 Handle Business Bill Offset Result
            if (offsetBillDetailInfo.getOffsetStatus() > 1) {
                if (offsetBillDetailInfo.getOffsetBillType().equals(1)) {
                    offsetContractDailyFeeBillDto.getWaitOffsetDeviceDailyBillInfoList().stream().filter(deviceDailyBillPo -> deviceDailyBillPo.getId().equals(offsetBillDetailInfo.getOffsetBillId())).findFirst().ifPresent(deviceDailyBillPo -> {
                        deviceDailyBillPo.setPaymentStatus(PaymentStatusEnum.getByValue(String.valueOf(offsetBillDetailInfo.getOffsetStatus())));
                        deviceDailyBillPo.setUnpaidAmount(deviceDailyBillPo.getPaidAmount().subtract(offsetBillDetailInfo.getActualOffsetAmount()));
                        deviceDailyBillPo.setPaidAmount(offsetBillDetailInfo.getActualOffsetAmount());
                    });
                } else {
                    offsetContractDailyFeeBillDto.getReceivableAdjustProfitLossInfoList().stream().filter(receivableAdjustProfitLossPo -> receivableAdjustProfitLossPo.getId().equals(offsetBillDetailInfo.getOffsetBillId())).findFirst().ifPresent(receivableAdjustProfitLossPo -> {
                        receivableAdjustProfitLossPo.setPaymentStatus(PaymentStatusEnum.getByValue(String.valueOf(offsetBillDetailInfo.getOffsetStatus())));
                        receivableAdjustProfitLossPo.setUnpaidAmount(receivableAdjustProfitLossPo.getPaidAmount().subtract(offsetBillDetailInfo.getActualOffsetAmount()));
                        receivableAdjustProfitLossPo.setPaidAmount(offsetBillDetailInfo.getActualOffsetAmount());
                    });
                }
            }
        }
    }

    private ContractOffsetRecordPo buildContractOffsetRecordPo(Long contractId, LocalDateTime feeIncurredTime, List<OffsetBillDetailInfo> offsetBillDetailInfoList, Long userId, String userName) {
        ContractOffsetRecordPo contractOffsetRecordPo = new ContractOffsetRecordPo();
        contractOffsetRecordPo.setContractId(contractId);
        contractOffsetRecordPo.setFeeIncurredTime(feeIncurredTime);
        contractOffsetRecordPo.setOffsetBillDetailInfos(offsetBillDetailInfoList);
        contractOffsetRecordPo.setCreatorId(userId);
        contractOffsetRecordPo.setCreatorName(userName);
        contractOffsetRecordPo.setUpdaterId(userId);
        contractOffsetRecordPo.setUpdaterName(userName);
        contractOffsetRecordPo.setCreateTime(LocalDateTime.now());
        contractOffsetRecordPo.setUpdateTime(LocalDateTime.now());
        return contractOffsetRecordPo;
    }

    /**
     * Build the OffsetBillDetailInfo object for the DeviceDailyBillPo object
     *
     * @param waitOffsetDeviceDailyBillInfo DeviceDailyBillPo object
     * @return OffsetBillDetailInfo object
     */
    private OffsetBillDetailInfo buildOffsetBillDetailInfo(DeviceDailyBillPo waitOffsetDeviceDailyBillInfo) {
        OffsetBillDetailInfo offsetBillDetailInfo = new OffsetBillDetailInfo();
        offsetBillDetailInfo.setOffsetBillId(waitOffsetDeviceDailyBillInfo.getId());
        offsetBillDetailInfo.setOffsetBillType(1);
        offsetBillDetailInfo.setBillTotalAmount(waitOffsetDeviceDailyBillInfo.getUnpaidAmount());
        offsetBillDetailInfo.setFeeIncurredTime(waitOffsetDeviceDailyBillInfo.getFeeIncurredTime());
        return offsetBillDetailInfo;
    }

    /**
     * Build the OffsetBillDetailInfo object for the ProfitLossRecordPo object
     *
     * @param receivableAdjustProfitLossInfo ProfitLossRecordPo object
     * @return OffsetBillDetailInfo object
     */
    private OffsetBillDetailInfo buildOffsetBillDetailInfo(ProfitLossRecordPo receivableAdjustProfitLossInfo) {
        OffsetBillDetailInfo offsetBillDetailInfo = new OffsetBillDetailInfo();
        offsetBillDetailInfo.setOffsetBillId(receivableAdjustProfitLossInfo.getId());
        offsetBillDetailInfo.setOffsetBillType(2);
        offsetBillDetailInfo.setBillTotalAmount(receivableAdjustProfitLossInfo.getUnpaidAmount());
        offsetBillDetailInfo.setFeeIncurredTime(receivableAdjustProfitLossInfo.getFeeIncurredTime());
        return offsetBillDetailInfo;
    }
}
